// Package nacos provides a nacos registry
package nacos

import (
	"errors"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"

	"strconv"
	"strings"
	"sync"
	//"time"

	"github.com/micro/go-micro/v2/config/cmd"
	"github.com/micro/go-micro/v2/registry"
	//"github.com/micro/go-micro/v2/util/log"
	log "github.com/micro/go-micro/v2/logger"

	//hash "github.com/mitchellh/hashstructure"
	mnet "github.com/micro/go-micro/v2/util/net"
	"github.com/nacos-group/nacos-sdk-go/common/constant"
	"github.com/nacos-group/nacos-sdk-go/vo"
)

var (
	prefix              = "/hms-registry"
	nacosDefIp          = "nacos-center.v-base"
	nacosDefPort uint64 = 30848
	nacosDefRegHash uint64 = 1
)

type nacosRegistry struct {
	client  naming_client.INamingClient
	options registry.Options
	sync.Mutex
	register map[string]uint64

	//param for registry
	params map[string] string

	//config for nacos
	serverConfigs []constant.ServerConfig
	//nacos命令默认参数，主要为Group Tenent cluster等
	defNameSpace  string	//optional,default:public
	defNameSpaceId  string	//optional,default:""
	defGroup   string	//optional,default:DEFAULT_GROUP
	defCluster string	//optional,default:DEFAULT
	defWatchGroup string   //默认监听的组名, 默认与defGroup相同， 影响 subscribe, getService getAllService
	defWatchClusterList []string
}

func init() {
	cmd.DefaultRegistries["nacos"] = NewRegistry
}

func toIpAddr(ip string, port uint64) string {
	strPort :=  strconv.FormatUint(port,10)
	return ip + ":" + strPort
}

//从 "ip:port" 的字符串中解析ip port 并返回
func getIpInfo(ipaddr string, defIp string , defPort uint64) (string, uint64){
	strInfo := strings.Split(ipaddr, ":")
	ip := defIp
	port := defPort

	if 2 == len(strInfo) {
		ip = strInfo[0]
		tmpPort, err := strconv.Atoi(strInfo[1])
		if nil != err {
			//port = nacosDefPort
			log.Error("Nacos bad port, use default port", ipaddr, ip, port)
		} else {
			port = uint64(tmpPort)
		}
	} else if 1 == len(strInfo) {
		ip = strInfo[0]
	}
	return ip, port
}

func configure(z *nacosRegistry, opts ...registry.Option) error {
	log.Info("==configure=========================")

	return nil
}

func (z *nacosRegistry) Init(opts ...registry.Option) error {
	log.Info("==Init=========================")
	return configure(z, opts...)
}

func (z *nacosRegistry) Options() registry.Options {
	return z.options
}

//func (z *nacosRegistry) Deregister(s *registry.Service) error {
func (z *nacosRegistry) Deregister(s *registry.Service, opts ...registry.DeregisterOption) error {
	log.Info("==Deregister=========================")
	if len(s.Nodes) == 0 {
		return errors.New("Require at least one node")
	}


	param := vo.DeregisterInstanceParam{
		//Ip:          ip,
		//Port:        port,
		//ServiceName: s.Name,
		Ephemeral: true,
		//Cluster: n.Id,
		//Tenant      string `param:"tenant"`      //optional
		//Cluster     string `param:"cluster"`     //optional,default:DEFAULT
		//GroupName   string `param:"groupName"`   //optional,default:DEFAULT_GROUP
		GroupName: z.defGroup,
		Tenant: z.defNameSpace,
	}

	for _, n := range s.Nodes {

    	ip,port := getIpInfo(n.Address, nacosDefIp, nacosDefPort)
		param.Ip = ip
		param.Port = port
		param.ServiceName = s.Name

		log.Warn("[Nacos DeRegister node]:", *n)
		regName := s.Name+n.Address
		z.Lock()
		delete(z.register, regName)
		z.Unlock()

		z.client.DeregisterInstance(param)
	}

	return nil
}

func (z *nacosRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {

	log.Debug("==Register=========================", *s)

	if len(s.Nodes) == 0 {
		return errors.New("Require at least one node")
	}

	var options registry.RegisterOptions
	for _, o := range opts {
		o(&options)
	}
	//log.Error("==Register Option=========================", z.params)

	for _, n := range s.Nodes {
		ip,port := getIpInfo(n.Address, nacosDefIp, nacosDefPort)
		param := vo.RegisterInstanceParam{
			Ip:          ip,
			Port:        port,
			ServiceName: s.Name,
			Weight:      10,
			//ClusterName: n.Id,
			Enable:    true,
			Healthy:   true,
			Ephemeral: true,
			Metadata:  n.Metadata,
			GroupName: z.defGroup,
			ClusterName: z.defCluster,
			Tenant: z.defNameSpace,
			//ClusterName: ,
			//Tenant      string            `param:"tenant"`      //optional
			//Metadata    map[string]string `param:"metadata"`    //optional
			//GroupName   string            `param:"groupName"`   //optional,default:DEFAULT_GROUP
		}
		//设置version
		if nil == param.Metadata {
			param.Metadata = map[string]string {
				"version": s.Version,
			}
		}else{
			param.Metadata["version"] = s.Version
		}

		//如果节点已经注册，不需要再注册， nacos sdk  内部进行心跳注册
		// check existing node
		regName := s.Name+n.Address

		z.Lock()
		v, ok := z.register[regName]
		z.Unlock()

		// the service is unchanged, skip registering
		if ok && v == nacosDefRegHash {
			//已经注册跳过
			continue
		}else {
			log.Info("[Nacos Register node]:", *n)
			flag, err:=z.client.RegisterInstance(param)
			if nil != err {
				log.Error("[Nacos Register node] error:", err, flag, *n)
			}	else{
				z.Lock()
				z.register[regName] = nacosDefRegHash
				z.Unlock()
			}

		}
	}

	return nil
}
/*
   type Service struct {
   	Dom             string            `json:"dom"`
   	CacheMillis     uint64            `json:"cacheMillis"`
   	UseSpecifiedURL bool              `json:"useSpecifiedUrl"`
   	Hosts           []Instance        `json:"hosts"`
   	Checksum        string            `json:"checksum"`
   	LastRefTime     uint64            `json:"lastRefTime"`
   	Env             string            `json:"env"`
   	Clusters        string            `json:"clusters"`
   	Metadata        map[string]string `json:"metadata"`
   	Name            string            `json:"name"`
   }

   type Service struct {
   	Name      string            `json:"name"`
   	Version   string            `json:"version"`
   	Metadata  map[string]string `json:"metadata"`
   	Endpoints []*Endpoint       `json:"endpoints"`
   	Nodes     []*Node           `json:"nodes"`
   }
*/
func (z *nacosRegistry) GetService(name string, opts ...registry.GetOption) ([]*registry.Service, error) {
	var options registry.GetOptions
	for _, o := range opts {
		o(&options)
	}
	withContext := false
	param := vo.GetServiceParam{
		GroupName: z.defWatchGroup,
		Clusters: z.defWatchClusterList,
	}
	if options.Context != nil {
		if p, ok := options.Context.Value("select_instances_param").(vo.GetServiceParam); ok {
			param = p
			withContext = ok
		}
	}
	//没有context，使用默认参数
	if !withContext {
		param.ServiceName = name
	}
	//log.Debug("==GetService=========================", param)

	nacosService, err := z.client.GetService(param)
	if err != nil {
		return nil, err
	}

	//log.Error("** GetService **",util.ToJsonString(nacosService), err)

	services := make([]*registry.Service, 0, 1)
	if nil == err {
		s := &registry.Service{
			Name:      nacosService.Dom,  //name 形如 DEFAULT_GROUP@@hms.ai.image.srv.face
			//Version:   "",
			Version:  nacosService.Metadata["version"],
			Metadata:  nacosService.Metadata,
			Endpoints: nil,
			Nodes:     nil,
		}

		for _, v := range nacosService.Hosts {
			if v.Valid {
				if v.Healthy {
				//ip := instance.Ip
				//port:= strconv.FormatUint(instance.Port, 10)
				//addr := ip + ":" + port
				node := &registry.Node{
					Id:       v.InstanceId,
					Address:   mnet.HostPort(v.Ip, v.Port),
					Metadata: v.Metadata,
				}

				if nil == node.Metadata{
					node.Metadata = make(map[string]string)
				}
				node.Metadata["cluster"] = v.ClusterName

				s.Nodes = append(s.Nodes, node)
				} else {
					log.Error("[Nacos Register node not healthy]:", v)
				}
			}
		}
		//log.Error("** GetService regService **", *s)
		services = append(services, s)

	}

	return services, err
}

func (z *nacosRegistry) ListServices(opts ...registry.ListOption) ([]*registry.Service, error) {
	log.Debug("==ListServices=========================")

	var options registry.ListOptions
	for _, o := range opts {
		o(&options)
	}
	withContext := false
	param := vo.GetAllServiceInfoParam{
		 GroupName: z.defWatchGroup,
		 NameSpace: z.defNameSpace,
	}
	if options.Context != nil {
		if p, ok := options.Context.Value("get_all_service_info_param").(vo.GetAllServiceInfoParam); ok {
			param = p
			withContext = ok
		}
	}
	if !withContext {
		services, err := z.client.GetAllServicesInfo(param)
		if err != nil {
			return nil, err
		}
		param.PageNo = 1
		param.PageSize = uint32(services.Count)
	}
	services, err := z.client.GetAllServicesInfo(param)
	if err != nil {
		return nil, err
	}
	var registryServices []*registry.Service
	for _, v := range services.Doms {
		registryServices = append(registryServices, &registry.Service{Name: v})
	}
	return registryServices, nil
}

func (z *nacosRegistry) String() string {
	return "nacos"
}

func (z *nacosRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
	log.Debug("==Watch=========================", len(opts))


	return newNacosWatcher(z, opts...)

	//return nil, errors.New("Watch unimplement")
}

func NewRegistry(opts ...registry.Option) registry.Registry {
	var options registry.Options
	for _, o := range opts {
		o(&options)
	}

	if options.Timeout == 0 {
		options.Timeout = 5
	}
	params := make(map[string]string)


	if nil != options.Context {
		p := options.Context.Value("params")
		if pp, ok := p.(map[string]string); ok {
			params = pp
		}
	}

	defNamespace := "PROD"
	defGroupName := "DEFAULT_GROUP"
	defCluster := "DEFAULT"
	defNamespaceId := "a85a37ef-5bec-478c-a60f-0b11f10b3da4"//默认生产环境
	defWatchGroup := defGroupName
	var defWatchClusterList  []string
	//GroupName 假如初始时已设置
	if  groupName, ok := params["regGroup"]; ok {
		defGroupName = groupName
	}
	if  watchGroupName, ok := params["watchGroup"]; ok {
		defWatchGroup = watchGroupName
	}else {
		defWatchGroup = defGroupName
	}

	if  clusterName, ok := params["regCluster"]; ok {
		defCluster = clusterName
	}

	if  tenant, ok := params["regNamespaceName"]; ok {
		defNamespace = tenant
	}
	//设置telent
	if  tenantId, ok := params["regNamespaceId"]; ok {
		defNamespaceId = tenantId
	}

	//获取服务时的cluser列表， 以分号区分，默认为空，获取全部列表
	if  watchClusters, ok := params["watchClusters"]; ok {
		//defWatchGroup = watchGroupName
		defWatchClusterList = strings.Split(watchClusters, ";")
		log.Info("==watchClusters================================", watchClusters, defWatchClusterList)
	}

	log.Info("==param================================", params)

	serverConfigs := []constant.ServerConfig{}
	//cAddrs := make([]string, 0, len(options.Addrs))
	for _, addr := range options.Addrs {
		if len(addr) == 0 {
			continue
		}
		//如果addr中含有“,” split为多个
		//如果addr中不含“,” split的结果为原来的字符串，不影响
		rc := strings.Split(addr, ",")
		for _, r := range rc {
			ip,port := getIpInfo(r, nacosDefIp, nacosDefPort)
			serverConfigs = append(serverConfigs, constant.ServerConfig{
				IpAddr: ip,
				//ContextPath: "/nacos",
				Port: port,
			})
		}
		//cAddrs = append(cAddrs, addr)
	}

	if len(serverConfigs) == 0 {
		serverConfigs = append(serverConfigs, constant.ServerConfig{
			IpAddr: nacosDefIp,
			//ContextPath: "/nacos",
			Port: nacosDefPort,
		})
	}
	//client 设置
	clientConfig := constant.ClientConfig{
		TimeoutMs:           5000, //z.options.Timeout
		BeatInterval: 30 * 1000,
		//ListenInterval:      10000,
		NotLoadCacheAtStart: true,
		LogDir:              "log/Registry",
		CacheDir:			 "cache",
		//Username:			 "nacos",
		//Password:			 "nacos",
		UpdateCacheWhenEmpty : true,   //update cache when get empty service instance from server
		NamespaceId: defNamespaceId,
	}
	//设置登录密码
	if  regUsername, ok := params["Username"]; ok {
		clientConfig.Username = regUsername
	}
	if  regPassword, ok := params["Password"]; ok {
		clientConfig.Password = regPassword
	}

	ncConfig := map[string]interface{}{
		"serverConfigs": serverConfigs,
		"clientConfig": clientConfig,
	}
	log.Info("======================================Nacos Registry Configs", ncConfig)
	c, _ := clients.CreateNamingClient(ncConfig)

	return &nacosRegistry{
		client:   c,
		options:  options,
		register: make(map[string]uint64),
		serverConfigs: serverConfigs,
		params: params,
		defNameSpace: defNamespace,
		defCluster: defCluster,
		defGroup: defGroupName,
		defWatchGroup:defWatchGroup,
		defNameSpaceId: defNamespaceId,
		defWatchClusterList: defWatchClusterList,
	}
}


